package com.atguigu.vip.spring.framework.context;

import com.atguigu.vip.spring.framework.annotation.GPAutowired;
import com.atguigu.vip.spring.framework.annotation.GPController;
import com.atguigu.vip.spring.framework.annotation.GPService;
import com.atguigu.vip.spring.framework.aop.GPAopProxy;
import com.atguigu.vip.spring.framework.aop.GPCglibAopProxy;
import com.atguigu.vip.spring.framework.aop.GPJdkDynamicAopProxy;
import com.atguigu.vip.spring.framework.aop.config.GPAopConfig;
import com.atguigu.vip.spring.framework.aop.support.GPAdvisedSupport;
import com.atguigu.vip.spring.framework.beans.GPBeanWrapper;
import com.atguigu.vip.spring.framework.beans.config.GPBeanDefinition;
import com.atguigu.vip.spring.framework.beans.config.GPBeanPostProcessor;
import com.atguigu.vip.spring.framework.beans.support.GPBeanDefinitionReader;
import com.atguigu.vip.spring.framework.beans.support.GPDefaultListableBeanFactory;
import com.atguigu.vip.spring.framework.core.GPBeanFactory;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: JLL
 * @Date: 2019/5/3 16:16
 *  源码中ApplicationContext 它是一个接口，这里写成实现类，理解就行。
 *
 *  这里按照之前源码分析的套路顺序：IOC -> DI - > MVC -> AOP
 *  先看IOC的步骤：定位、加载、注册
 *
 *
 */
public class GPApplicationContext extends GPDefaultListableBeanFactory implements GPBeanFactory {


    private String [] configLoactions;
    private GPBeanDefinitionReader reader;

    //单例的IOC容器缓存
    private Map<String,Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>();

    /*//单例的IOC容器缓存
    private Map<String,Object> singletonObjects = new ConcurrentHashMap<>();*/
    //通用的IOC容器  名字和源码保持一直 factoryBeanInstanceCache
    private Map<String,GPBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();


    public GPApplicationContext(String... configLocations) {
        this.configLoactions = configLocations;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void refresh() throws Exception {
        //1、定位，定位配置文件...
        reader = new GPBeanDefinitionReader(this.configLoactions);

        //2、加载配置文件，扫描相关的类，把它们封装成BeanDefinition、、
        List<GPBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

        //3、注册，把配置信息放到容器里面（伪IOC容器） 为什么是伪，因为真正的IOC容器是在DI的getBean开始的
        doRegisterBeanDefinition(beanDefinitions);

        //4、把不是延时加载的类，有提前初始化、 核心就是getBean() 方法
        doAutowrited();

    }

    //只处理非延时加载的情况   这里重点是getBean方法，就完成了注入
    //延迟加载的呢？ 这里就不处理，后续 你自己 getBean的时候再实例化加到beanFactory里去呗
    private void doAutowrited() {
        for (Map.Entry<String, GPBeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            //不是延迟加载，则调用getBean。
            if(!beanDefinitionEntry.getValue().isLazyInit()) {
                try {
                    getBean(beanName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private void doRegisterBeanDefinition(List<GPBeanDefinition> beanDefinitions) throws Exception {
        /**
         * 注意这个beanDefinitionMap是在它的父类中定义的
         */
        for (GPBeanDefinition beanDefinition: beanDefinitions) {
            if(super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())){
                throw new Exception("The “" + beanDefinition.getFactoryBeanName() + "” is exists!!");
            }
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
        }
        //到这里为止，容器初始化完毕
    }

    @Override
    public Object getBean(Class<?> beanClass)  throws Exception {
        return getBean(beanClass.getName());
    }

    /**
     * getBean只是一个依赖注入的方法，还有一个很重要的方法那就是refresh()方法
     */
    //依赖注入，从这里开始，通过读取BeanDefinition中的信息
    //然后，通过反射机制创建一个实例并返回
    //Spring做法是，不会把最原始的对象放出去，会用一个BeanWrapper来进行一次包装
    //装饰器模式：
    //1、保留原来的OOP关系
    //2、我需要对它进行扩展，增强（为了以后AOP打基础）
    @Override
    public Object getBean(String beanName) throws Exception{


        GPBeanDefinition gpBeanDefinition = this.beanDefinitionMap.get(beanName);
        Object instance = null;

        //这个逻辑还不严谨（只是介绍下GPBeanPostProcessor的基本概念），自己可以去参考Spring源码
        //工厂模式 + 策略模式
        GPBeanPostProcessor postProcessor = new GPBeanPostProcessor();
        //bean创建之前通知一下
        postProcessor.postProcessBeforeInitialization(instance,beanName);

        instance = instantiateBean(beanName,gpBeanDefinition);


        //factoryBeanInstanceCache

        //4、把BeanWrapper存到IOC容器里面
//        //1、初始化
        GPBeanWrapper beanWrapper = new GPBeanWrapper(instance);
        /**
         * 思考：为啥初始化 和 注入不写在一个方法里呢，非写2个方法呢？
         * 因为依赖注入。 详情见 springv2.0-IOC和DI笔记-19年补充.txt
         *
         */
//        //class A{ B b;}
//        //class B{ A a;}
//        //先有鸡还是先有蛋的问题，一个方法是搞不定的，要分两次

        //2、拿到BeanWraoper之后，把BeanWrapper保存到IOC容器中去
        this.factoryBeanInstanceCache.put(beanName,beanWrapper);

        postProcessor.postProcessAfterInitialization(instance,beanName);

//        //3、注入
        /**
         * 擦，spring源码里 populateBean是在instantiateBean之前调用啊。。  我日，这里是不是写错了。
         */
        populateBean(beanName,new GPBeanDefinition(),beanWrapper);


        return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
    }

    /**
     * 创建好的bean封装到 BeanWrapper里，是为了解决依赖注入的？
     */
    private void populateBean(String beanName, GPBeanDefinition gpBeanDefinition, GPBeanWrapper gpBeanWrapper) {
        Object instance = gpBeanWrapper.getWrappedInstance();

//        gpBeanDefinition.getBeanClassName();

        Class<?> clazz = gpBeanWrapper.getWrappedClass();
        //判断只有加了注解的类，才执行依赖注入
        if(!(clazz.isAnnotationPresent(GPController.class) || clazz.isAnnotationPresent(GPService.class))){
            return;
        }

        //获得所有的fields
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if(!field.isAnnotationPresent(GPAutowired.class)){ continue;}

            GPAutowired autowired = field.getAnnotation(GPAutowired.class);

            String autowiredBeanName =  autowired.value().trim();
            if("".equals(autowiredBeanName)){
                autowiredBeanName = field.getType().getName();
            }

            //强制访问
            field.setAccessible(true);

            try {
                //等于null，就先不注入
                //为什么会为NULL，先留个坑
                if(this.factoryBeanInstanceCache.get(autowiredBeanName) == null){ continue; }
//                if(instance == null){
//                    continue;
//                }
                field.set(instance,this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }

    }


    /**
     *作用是读取bd里的配置再转成实体对象 再存到真正的ioc容器 key就是beanName，value就是实例
     */
    private Object instantiateBean(String beanName, GPBeanDefinition gpBeanDefinition) {
        //1、拿到要实例化的对象的类名
        String className = gpBeanDefinition.getBeanClassName();

        //2、反射实例化，得到一个对象
        Object instance = null;
        try {
//            gpBeanDefinition.getFactoryBeanName()
            //假设默认就是单例,细节暂且不考虑，先把主线拉通
            if(this.factoryBeanObjectCache.containsKey(className)){
                instance = this.factoryBeanObjectCache.get(className);
            }else {
                Class<?> clazz = Class.forName(className);
                //这里没考虑 带参数的构造函数啊。 tom这里还是没想到
                instance = clazz.newInstance();

                /**
                 * 这里将 AOP和IOC 打通
                 */
                GPAdvisedSupport config = instantionAopConfig(gpBeanDefinition);
                config.setTargetClass(clazz);
                config.setTarget(instance);

                //符合PointCut的规则的话，就创建代理对象
                if(config.pointCutMatch()) {
                    instance = createProxy(config).getProxy();
                }

                /**
                 * 缓存的思想，单机就存一个map里，集群就存中间件redis了。
                 */
                this.factoryBeanObjectCache.put(className,instance);
                this.factoryBeanObjectCache.put(gpBeanDefinition.getFactoryBeanName(),instance);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return instance;
    }

    private GPAopProxy createProxy(GPAdvisedSupport config) {

        Class targetClass = config.getTargetClass();
        if(targetClass.getInterfaces().length > 0){
            return new GPJdkDynamicAopProxy(config);
        }
        return new GPCglibAopProxy(config);
    }

    /**
     *
     */
    private GPAdvisedSupport instantionAopConfig(GPBeanDefinition gpBeanDefinition) {
        GPAopConfig config = new GPAopConfig();
        config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
        config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
        config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new GPAdvisedSupport(config);
    }

    /**
     * 这里要注意，不能直接把map返回出去。 最少知道原则，要是返回map，那IOC容器不是人人都去改了吗。太危险了。
     * @return
     */
    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount(){
        return this.beanDefinitionMap.size();
    }

    public Properties getConfig(){
        return this.reader.getConfig();
    }
}
